// BigInteger monkey patching
BigInteger.valueOf = function (x) {
    if (!x) return BigInteger.ZERO;
    return new BigInteger(''+x, 10);
}

/**
 * Returns a byte array representation of the big integer.
 *
 * This returns the absolute of the contained value in big endian
 * form. A value of zero results in an empty array.
 */
BigInteger.prototype.toByteArrayUnsigned = function () {
    var ba = this.abs().toByteArray();
    if (ba.length) {
        if (ba[0] == 0) {
            ba = ba.slice(1);
        }
        return ba.map(function (v) {
            return (v < 0) ? v + 256 : v;
        });
    } else {
        // Empty array, nothing to do
        return ba;
    }
};

/**
 * Turns a byte array into a big integer.
 *
 * This function will interpret a byte array as a big integer in big
 * endian notation and ignore leading zeros.
 */
BigInteger.fromByteArrayUnsigned = function (ba) {
    if (!ba.length) {
        return ba.valueOf(0);
    } else if (ba[0] & 0x80) {
        // Prepend a zero so the BigInteger class doesn't mistake this
        // for a negative integer.
        return new BigInteger([0].concat(ba));
    } else {
        return new BigInteger(ba);
    }
};

/**
 * Converts big integer to signed byte representation.
 *
 * The format for this value uses a the most significant bit as a sign
 * bit. If the most significant bit is already occupied by the
 * absolute value, an extra byte is prepended and the sign bit is set
 * there.
 *
 * Examples:
 *
 *      0 =>     0x00
 *      1 =>     0x01
 *     -1 =>     0x81
 *    127 =>     0x7f
 *   -127 =>     0xff
 *    128 =>   0x0080
 *   -128 =>   0x8080
 *    255 =>   0x00ff
 *   -255 =>   0x80ff
 *  16300 =>   0x3fac
 * -16300 =>   0xbfac
 *  62300 => 0x00f35c
 * -62300 => 0x80f35c
 */
BigInteger.prototype.toByteArraySigned = function () {
    var val = this.abs().toByteArrayUnsigned();
    var neg = this.compareTo(BigInteger.ZERO) < 0;

    if (neg) {
        if (val[0] & 0x80) {
            val.unshift(0x80);
        } else {
            val[0] |= 0x80;
        }
    } else {
        if (val[0] & 0x80) {
            val.unshift(0x00);
        }
    }

    return val;
};

/**
 * Parse a signed big integer byte representation.
 *
 * For details on the format please see BigInteger.toByteArraySigned.
 */
BigInteger.fromByteArraySigned = function (ba) {
    // Check for negative value
    if (ba[0] & 0x80) {
        // Remove sign bit
        ba[0] &= 0x7f;

        return BigInteger.fromByteArrayUnsigned(ba).negate();
    } else {
        return BigInteger.fromByteArrayUnsigned(ba);
    }
};

// Bitcoin utility functions
Bitcoin.Util = {
    /**
     * Cross-browser compatibility version of Array.isArray.
     */
    isArray: Array.isArray || function(o)
    {
        return Object.prototype.toString.call(o) === '[object Array]';
    },

    /**
     * Create an array of a certain length filled with a specific value.
     */
    makeFilledArray: function (len, val)
    {
        var array = [];
        var i = 0;
        while (i < len) {
            array[i++] = val;
        }
        return array;
    },

    /**
     * Turn an integer into a "var_int".
     *
     * "var_int" is a variable length integer used by Bitcoin's binary format.
     *
     * Returns a byte array.
     */
    numToVarInt: function (i)
    {
        if (i < 0xfd) {
            // unsigned char
            return [i];
        } else if (i <= 1<<16) {
            // unsigned short (LE)
            return [0xfd, i & 255, i >>> 8]; // BitcoinQT wants big endian here
        } else if (i <= 1<<32) {
            // unsigned int (LE)
            return [0xfe].concat(Crypto.util.wordsToBytes([i]));
        } else {
            // unsigned long long (LE)
            return [0xff].concat(Crypto.util.wordsToBytes([i >>> 32, i]));
        }
    },

    /**
     * Parse a Bitcoin value byte array, returning a BigInteger.
     */
    valueToBigInt: function (valueBuffer)
    {
        if (valueBuffer instanceof BigInteger) return valueBuffer;

        // Prepend zero byte to prevent interpretation as negative integer
        return BigInteger.fromByteArrayUnsigned(valueBuffer);
    },

    /**
     * Format a Bitcoin value as a string.
     *
     * Takes a BigInteger or byte-array and returns that amount of Bitcoins in a
     * nice standard formatting.
     *
     * Examples:
     * 12.3555
     * 0.1234
     * 900.99998888
     * 34.00
     */
    formatValue: function (valueBuffer) {
        var value = this.valueToBigInt(valueBuffer).toString();

        var integerPart = value.length > 8 ? value.substr(0, value.length-8) : '0';
        var decimalPart = value.length > 8 ? value.substr(value.length-8) : value;

        while (decimalPart.length < 8) decimalPart = "0"+decimalPart;
        decimalPart = decimalPart.replace(/0*$/, '');
        while (decimalPart.length < 2) decimalPart += "0";
        return integerPart+"."+decimalPart;
    },

    /**
     * Parse a floating point string as a Bitcoin value.
     *
     * Keep in mind that parsing user input is messy. You should always display
     * the parsed value back to the user to make sure we understood his input
     * correctly.
     */
    parseValue: function (valueString) {
        if (!valueString) return BigInteger.ZERO;

        valueString = ''+valueString;

        if (!/^[\d.]+$/.test(valueString)) {
            return BigInteger.ZERO;
        }

        // TODO: Detect other number formats (e.g. comma as decimal separator)
        var valueComp = valueString.split('.');
        var integralPart = valueComp[0];
        var fractionalPart = valueComp[1] || "0";

        fractionalPart = fractionalPart.length > 8 ? fractionalPart.substr(0, 8) : fractionalPart;

        while (fractionalPart.length < 8) fractionalPart += "0";

        fractionalPart = fractionalPart.replace(/^0+/g, '');
        var value = BigInteger.valueOf(integralPart);
        value = value.multiply(BigInteger.valueOf(100000000));
        value = value.add(BigInteger.valueOf(fractionalPart));
        return value;
    },

    /**
     * Calculate RIPEMD160(SHA256(data)).
     *
     * Takes an arbitrary byte array as inputs and returns the hash as a byte
     * array.
     */
    sha256ripe160: function (data) {
        return Crypto.RIPEMD160(Crypto.SHA256(data, {asBytes: true}), {asBytes: true});
    }
};

for (var i in Crypto.util) {
    if (Crypto.util.hasOwnProperty(i)) {
        Bitcoin.Util[i] = Crypto.util[i];
    }
}
